{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "95f0a171",
   "metadata": {},
   "source": [
    "(strings)=\n",
    "# Strings and Text\n",
    "\n",
    "## Introduction\n",
    "\n",
    "Strings are just the data type for text. So far, you've used strings but without learning much about the details. Now it's time to dive into them, learning what makes strings tick, and mastering some of the powerful string manipulation tools you have at your disposal.\n",
    "\n",
    "This chapter has benefitted from the [Python String Cook Book](https://mkaz.blog/code/python-string-format-cookbook/) and Jake VanderPlas' [Python Data Science Handbook](https://jakevdp.github.io/PythonDataScienceHandbook/03.10-working-with-strings.html).\n",
    "\n",
    "Note that there are more powerful methods for working with strings called *regular expressions* but these will be covered in a different chapter."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3fc1cd0f",
   "metadata": {},
   "source": [
    "## Creating Strings\n",
    "\n",
    "We've created strings in passing earlier in the book, but didn't discuss the details. First, you can create a string using either single quotes (`'`) or double quotes (`\"`). It's good to be consistent in this, even if it doesn't matter which you use, but automatic code formatters tend to prefer `\"`. If you have a quote inside a string, use `'` within it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "478a847a",
   "metadata": {},
   "outputs": [],
   "source": [
    "string_one = \"This is a string\"\n",
    "string_two = (\n",
    "    \"If I want to include a 'quote' inside a string, I use double quotes on the outside\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a146322",
   "metadata": {},
   "source": [
    "Strings are of type `str`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d7f4ea2d",
   "metadata": {},
   "outputs": [],
   "source": [
    "type(string_one)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2bc26125",
   "metadata": {},
   "source": [
    "Strings in Python can be indexed, so we can get certain characters out by using square brackets to say which positions we would like."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "01379fe7",
   "metadata": {},
   "outputs": [],
   "source": [
    "var = \"banana\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d88f7928",
   "metadata": {},
   "outputs": [],
   "source": [
    "var[:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d76d0c0a",
   "metadata": {},
   "source": [
    "The usual slicing tricks that apply to lists work for strings too, i.e. the positions you want to get can be retrieved using the `var[start:stop:step]` syntax. Here's an example of getting every other character from the string starting from the 2nd position."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e03d95d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "var[1::2]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ddd1bbe7",
   "metadata": {},
   "source": [
    "Note that strings, like tuples such as `(1, 2, 3)` but unlike lists such as `[1, 2, 3]`, are *immutable*. This means commands like `var[0] = \"B\"` will result in an error. If you want to change a single character, you will have to replace the entire string. In this example, the command to do that would be `var = \"Banana\"`.\n",
    "\n",
    "Like lists, you can find the length of a string using `len()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "83ab201b",
   "metadata": {},
   "outputs": [],
   "source": [
    "len(var)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7eeebc05",
   "metadata": {},
   "source": [
    "You can concatenate strings using the `+` operator:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7801bd5d",
   "metadata": {},
   "outputs": [],
   "source": [
    "string_one + \". \" + string_two + \".\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e8fb0ab8",
   "metadata": {},
   "source": [
    "Note that we added extra characters so that the phrase made sense. Another way of achieving the same end that scales to many words or phrases more efficiently (if you have them in a list) is:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "138cef18",
   "metadata": {},
   "outputs": [],
   "source": [
    "\". \".join([string_one, string_two])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "77b33c02",
   "metadata": {},
   "source": [
    "Three useful functions to know about are `upper()`, `lower()`, and `title()`. Let's see what they do\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e11896f8",
   "metadata": {},
   "outputs": [],
   "source": [
    "var = \"input TEXT\"\n",
    "var_list = [var.upper(), var.lower(), var.title()]\n",
    "print(var_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "505bec9e",
   "metadata": {},
   "source": [
    "Note that there are many built-in functions for using strings in Python, you can find a comprehensive list [here](https://www.w3schools.com/python/python_ref_string.asp).\n",
    "\n",
    "```{admonition} Exercise\n",
    "Reverse the string `\"gnirts desrever a si sihT\"` using indexing operations.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d70a3f6",
   "metadata": {},
   "source": [
    "While we're using `print()`, it has a few tricks. If we have a list, we can print out entries with a given separator:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bf0aadec",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(*var_list, sep=\"; and \\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "08d9b5af",
   "metadata": {},
   "source": [
    "(We'll find out more about what '\\n' does shortly.) To turn variables of other kinds into strings, use the `str()` function, for example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a96f048c",
   "metadata": {},
   "outputs": [],
   "source": [
    "(\n",
    "    \"A boolean is either \"\n",
    "    + str(True)\n",
    "    + \" or \"\n",
    "    + str(False)\n",
    "    + \", there are only \"\n",
    "    + str(2)\n",
    "    + \" options.\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c79246f",
   "metadata": {},
   "source": [
    "In this example two boolean variables and one integer variable were converted to strings. `str()` generally makes an intelligent guess at how you'd like to convert your non-string type variable into a string type. You can pass a variable or a literal value to `str()`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e68633d9",
   "metadata": {},
   "source": [
    "### f-strings\n",
    "\n",
    "The example above is quite verbose. Another way of combining strings with variables is via *f-strings*. A simple f-string looks like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9dddf0da",
   "metadata": {},
   "outputs": [],
   "source": [
    "variable = 15.32399\n",
    "print(f\"You scored {variable}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "027287af",
   "metadata": {},
   "source": [
    "This is similar to calling `str` on variable and using `+` for concatenation but much shorter to write. You can add expressions to f-strings too:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "795e7c07",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f\"You scored {variable**2}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe120dd7",
   "metadata": {},
   "source": [
    "This also works with functions; after all `**2` is just a function with its own special syntax."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3524d86",
   "metadata": {},
   "source": [
    "In this example, the score number that came out had a lot of (probably) uninteresting decimal places. So how do we polish the printed output? You can pass more inforation to the f-string to get the output formatted just the way you want. Let's say we wanted two decimal places and a sign (although you always write `+` in the formatting, the sign comes out as + or - depending on the value):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1f3d3806",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f\"You scored {variable:+.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04eb84a8",
   "metadata": {},
   "source": [
    "There are a whole range of formatting options for numbers as shown in the following table:\n",
    "\n",
    "| Number     \t| Format  \t| Output     \t| Description                                   \t|\n",
    "|------------\t|---------\t|------------\t|-----------------------------------------------\t|\n",
    "| 15.32347  \t| {:.2f}  \t| 15.32       \t| Format float 2 decimal places                 \t|\n",
    "| 15.32347  \t| {:+.2f} \t| +15.32      \t| Format float 2 decimal places with sign       \t|\n",
    "| -1         \t| {:+.2f} \t| -1.00      \t| Format float 2 decimal places with sign       \t|\n",
    "| 15.32347    \t| {:.0f}  \t| 15          \t| Format float with no decimal places           \t|\n",
    "| 3          \t| {:0>2d} \t| 03         \t| Pad number with zeros (left padding, width 2) \t|\n",
    "| 3          \t| {:*<4d} \t| 3***       \t| Pad number with *’s (right padding, width 4)  \t|\n",
    "| 13         \t| {:*<4d} \t| 13**       \t| Pad number with *’s (right padding, width 4)  \t|\n",
    "| 1000000    \t| {:,}    \t| 1,000,000  \t| Number format with comma separator            \t|\n",
    "| 0.25       \t| {:.1%}  \t| 25.0%     \t| Format percentage                             \t|\n",
    "| 1000000000 \t| {:.2e}  \t| 1.00e+09   \t| Exponent notation                             \t|\n",
    "| 12         \t| {:10d}  \t|            12 | Right aligned (default, width 10)             \t|\n",
    "| 12         \t| {:<10d} \t| 12            | Left aligned (width 10)                       \t|\n",
    "| 12         \t| {:^10d} \t|      12       | Center aligned (width 10)                     \t|\n",
    "\n",
    "As well as using this page interactively through the Colab and Binder links at the top of the page, or downloading this page and using it on your own computer, you can play around with some of these options over at [this link](https://www.python-utils.com/)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ae30791",
   "metadata": {},
   "source": [
    "### Special Characters and How to Escape Strings"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2fb21df1",
   "metadata": {},
   "source": [
    "Python has a string module that comes with some useful built-in strings and characters. For example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ccd65aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "import string\n",
    "\n",
    "string.punctuation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e9646e6",
   "metadata": {},
   "source": [
    "gives you all of the punctuation,"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16205c36",
   "metadata": {},
   "outputs": [],
   "source": [
    "string.ascii_letters"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1cb1dac5",
   "metadata": {},
   "source": [
    "returns all of the basic letters in the 'ASCII' encoding (with `.ascii_lowercase` and `.ascii_uppercase` variants), and"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0c67f5cd",
   "metadata": {},
   "outputs": [],
   "source": [
    "string.digits"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "589fff6d",
   "metadata": {},
   "source": [
    "gives you the numbers from 0 to 9. Finally, though less impressive visually, `string.whitespace` gives a string containing all of the different (there is more than one!) types of whitespace."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "794c4f7e",
   "metadata": {},
   "source": [
    "There are other special characters around; in fact, we already met the most famous of them: \"\\n\" for new line. To actually print \"\\n\" we have to 'escape' the backward slash by adding another backward slash:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16e9904a",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Here is a \\n new line\")\n",
    "print(\"Here is an \\\\n escaped new line \")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf878d74",
   "metadata": {},
   "source": [
    "The table below shows the most important escape commands:\n",
    "\n",
    "| Code \t| Result          \t|\n",
    "|------\t|-----------------\t|\n",
    "| `\\'`   \t| Single Quote (useful if using `'` for strings)   \t|\n",
    "| `\\\"`      | Double Quote (useful if using `\"` for strings)   \t|\n",
    "| `\\\\`   \t| Backslash       \t|\n",
    "| `\\n`   \t| New Line        \t|\n",
    "| `\\r`   \t| Carriage Return \t|\n",
    "| `\\t`   \t| Tab             \t|"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c7dd19ac",
   "metadata": {},
   "source": [
    "Here's a more complicated example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af423bd1",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"a\\tb\\nA\\tB\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "16c97e01",
   "metadata": {},
   "source": [
    "### Raw Strings\n",
    "\n",
    "Strings prefixed with `r` such as r'...' and r\"...\" are called raw strings and treat backslashes \\ as literal characters rather than special characters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c2b9c689",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(r\"a\\tb\\nA\\tB\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98d896ed",
   "metadata": {},
   "source": [
    "## Cleaning Text\n",
    "\n",
    "You often want to make changes to the text you're working with. In this section, we'll look at the various options to do this.\n",
    "\n",
    "### Replacing sub-strings\n",
    "\n",
    "A common text task is to replace a substring within a longer string. Let's say you have a string variable `var`. You can use `.replace(old_text, new_text)` to do this.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "229ada3a",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"Value is objective\".replace(\"objective\", \"subjective\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ce0467a1",
   "metadata": {},
   "source": [
    "As with any variable of a specific type (here, string), this would also work with variables:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79f754dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "text = \"Value is objective\"\n",
    "old_substr = \"objective\"\n",
    "new_substr = \"subjective\"\n",
    "text.replace(old_substr, new_substr)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c5ab4c1",
   "metadata": {},
   "source": [
    "Note that `.replace()` performs an exact replace and so is case-sensitive."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "85bba3f9",
   "metadata": {},
   "source": [
    "### Replacing characters with translate\n",
    "\n",
    "A character is an individual entry within a string, like the 'l' in 'equilibrium'. You can always count the number of characters in a string variable called `var` by using `len(var)`. A very fast method for replacing individual characters in a string is `str.translate()`. \n",
    "\n",
    "Replacing characters is extremely useful in certain situations, most commonly when you wish to remote all punctuation prior to doing other text analysis. You can use the built-in `string.punctuation` for this.\n",
    "\n",
    "Let's see how to use it to remove all of the vowels from some text. With apologies to economist Lisa Cook, we'll use the abstract from {cite:t}`cook2011inventing` as the text we'll modify and we'll first create a dictionary of translations of vowels to nothing, i.e. `\"\"`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "99675fee",
   "metadata": {},
   "outputs": [],
   "source": [
    "example_text = \"Much recent work has focused on the influence of social capital on innovative outcomes. Little research has been done on disadvantaged groups who were often restricted from participation in social networks that provide information necessary for invention and innovation. Unique new data on African American inventors and patentees between 1843 and 1930 permit an empirical investigation of the relation between social capital and economic outcomes. I find that African Americans used both traditional, i.e., occupation-based, and nontraditional, i.e., civic, networks to maximize inventive output and that laws constraining social-capital formation are most negatively correlated with economically important inventive activity.\"\n",
    "vowels = \"aeiou\"\n",
    "translation_dict = {x: \"\" for x in vowels}\n",
    "translation_dict"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "391d2748",
   "metadata": {},
   "source": [
    "Now we turn our dictionary into a string translator and apply it to our text:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e48763cb",
   "metadata": {},
   "outputs": [],
   "source": [
    "translator = example_text.maketrans(translation_dict)\n",
    "example_text.translate(translator)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1c0359ed",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "Use `translate` to replace all puncuation from the following sentence with spaces: \"The well-known story I told at the conferences [about hypocondria] in Boston, New York, Philadelphia,...and Richmond went as follows: It amused people who knew Tommy to hear this; however, it distressed Suzi when Tommy (1982--2019) asked, \\\"How can I find out who yelled, 'Fire!' in the theater?\\\" and then didn't wait to hear Missy give the answer---'Dick Tracy.'\"\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f27549e8",
   "metadata": {},
   "source": [
    "Generally, `str.translate` is very fast at replacing individual characters in strings. But you can also do it using a list comprehension and a `join()` of the resulting list, like so:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ac758b38",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\".join(\n",
    "    [\n",
    "        ch\n",
    "        for ch in \"Example. string. with- excess_ [punctuation]/,\"\n",
    "        if ch not in string.punctuation\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbe854ee",
   "metadata": {},
   "source": [
    "### Splitting strings\n",
    "\n",
    "If you want to split a string at a certain position, there are two quick ways to do it. The first is to use indexing methods, which work well if you know at which position you want to split text, eg\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "122619bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"This is a sentence and we will split it at character 18\"[:18]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ddc2c15",
   "metadata": {},
   "source": [
    "Next up we can use the built-in `split()` function, which returns a list of places where a given sub-string occurs:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9fc432ed",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"This is a sentence. And another sentence. And a third sentence\".split(\".\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "992b6a79",
   "metadata": {},
   "source": [
    "Note that the character used to split the string is removed from the resulting list of strings. Let's see an example with a string used for splitting instead of a single character:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6904e486",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"This is a sentence. And another sentence. And a third sentence\".split(\"sentence\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d7beb3c",
   "metadata": {},
   "source": [
    "A useful extra function to know about is `splitlines()`, which splits a string at line breaks and returns the split parts as a list."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "752b9704",
   "metadata": {},
   "source": [
    "### count and find\n",
    "\n",
    "Let's do some simple counting of words within text using `str.count()`. Let's use the first verse of Elizabeth Bishop's sestina 'A Miracle for Breakfast' for our text."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "22f94993",
   "metadata": {},
   "outputs": [],
   "source": [
    "text = \"At six o'clock we were waiting for coffee, \\n waiting for coffee and the charitable crumb \\n that was going to be served from a certain balcony \\n --like kings of old, or like a miracle. \\n It was still dark. One foot of the sun \\n steadied itself on a long ripple in the river.\"\n",
    "word = \"coffee\"\n",
    "print(f'The word \"{word}\" appears {text.count(word)} times.')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "378dade0",
   "metadata": {},
   "source": [
    "Meanwhile, `find()` returns the position where a particular word or character occurs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a351a11b",
   "metadata": {},
   "outputs": [],
   "source": [
    "text.find(word)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7fb9ae26",
   "metadata": {},
   "source": [
    "We can check this using the number we get and some string indexing:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8e0a7020",
   "metadata": {},
   "outputs": [],
   "source": [
    "text[text.find(word) : text.find(word) + len(word)]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ecc16d15",
   "metadata": {},
   "source": [
    "But this isn't the only place where the word 'coffee' appears. If we want to find the last occurrence, it's"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e18f64a3",
   "metadata": {},
   "outputs": [],
   "source": [
    "text.rfind(word)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f30ef28a",
   "metadata": {},
   "source": [
    "## Working with Multiple Strings"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09f7c915",
   "metadata": {},
   "source": [
    "We've seen how to work with individual strings. But often we want to work with a group of strings, otherwise known as a corpus, that is a collection of texts. It could be a collection of words, sentences, paragraphs, or some domain-based grouping (eg job descriptions). Just like any other Python object, you can put strings into a list (or other iterable).\n",
    "\n",
    "And, fortunately, many of the methods that we have seen deployed on a single string can be straightforwardly scaled up to hundreds, thousands, or millions of strings using **pandas** or other tools. This scaling up is achieved via *vectorisation*, in analogy with going from a single value (a scalar) to multiple values in a list (a vector).\n",
    "\n",
    "As a very minimal example, here is capitalisation of names vectorised using a list comprehension:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bbc3eb7b",
   "metadata": {},
   "outputs": [],
   "source": [
    "[name.capitalize() for name in [\"ada\", \"adam\", \"elinor\", \"grace\", \"jean\"]]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "730df2c8",
   "metadata": {},
   "source": [
    "A **pandas** series can be used in place of a list. Let's create the series first:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c8a7f68b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "dfs = pd.Series(\n",
    "    [\"ada lovelace\", \"adam smith\", \"elinor ostrom\", \"grace hopper\", \"jean bartik\"],\n",
    "    dtype=\"string\",\n",
    ")\n",
    "dfs"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "222fbb51",
   "metadata": {},
   "source": [
    "Now we use the syntax series.str.function to change the text series:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7cf149b5",
   "metadata": {},
   "outputs": [],
   "source": [
    "dfs.str.title()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec439fc1",
   "metadata": {},
   "source": [
    "If we had a data frame and not a series, the syntax would change to refer just to the column of interest like so:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "26dc9a7b",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(dfs, columns=[\"names\"])\n",
    "df[\"names\"].str.title()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e430dd9",
   "metadata": {},
   "source": [
    "The table below shows a non-exhaustive list of the string methods that are available in **pandas**.\n",
    "\n",
    "| Function (preceded by `.str.`) | What it does |\n",
    "|-----------------------------|-------------------------|\n",
    "| `len()` | Length of string. |\n",
    "| `lower()` | Put string in lower case. |\n",
    "| `upper()` | Put string in upper case. |\n",
    "| `capitalize()` | Put string in leading upper case. |\n",
    "| `swapcase()` | Swap cases in a string. |\n",
    "| `translate()` | Returns a copy of the string in which each character has been mapped through a given translation table. |\n",
    "| `ljust()` | Left pad a string (default is to pad with spaces) |\n",
    "| `rjust()` | Right pad a string (default is to pad with spaces) |\n",
    "| `center()` | Pad such that string appears in centre (default is to pad with spaces) |\n",
    "| `zfill()` | Pad with zeros |\n",
    "| `strip()` | Strip out leading and trailing whitespace |\n",
    "| `rstrip()` | Strip out trailing whitespace |\n",
    "| `lstrip()` | Strip out leading whitespace |\n",
    "| `find()` | Return the lowest index in the data where a substring appears |\n",
    "| `split()` | Split the string using a passed substring as the delimiter |\n",
    "| `isupper()` | Check whether string is upper case |\n",
    "| `isdigit()` | Check whether string is composed of digits |\n",
    "| `islower()` | Check whether string is lower case |\n",
    "| `startswith()` | Check whether string starts with a given sub-string |\n",
    "\n",
    "Regular expressions can also be scaled up with **pandas**. The below table shows vectorised regular expressions.\n",
    "\n",
    "| Function | What it does |\n",
    "|-|----------------------------------|\n",
    "| `match()` | Call `re.match()` on each element, returning a boolean. |\n",
    "| `extract()` | Call `re.match()` on each element, returning matched groups as strings. |\n",
    "| `findall()` | Call `re.findall()` on each element |\n",
    "| `replace()` | Replace occurrences of pattern with some other string |\n",
    "| `contains()` | Call `re.search()` on each element, returning a boolean |\n",
    "| `count()` | Count occurrences of pattern |\n",
    "| `split()` | Equivalent to `str.split()`, but accepts regexes |\n",
    "| `rsplit()` | Equivalent to `str.rsplit()`, but accepts regexes |\n",
    "\n",
    "\n",
    "Let's see a couple of these in action. First, splitting on a given sub-string:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d7a29663",
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"names\"].str.split(\" \")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "597c0d23",
   "metadata": {},
   "source": [
    "It's fairly common that you want to split out strings and save the results to new columns in your data frame. You can specify a (max) number of splits via the `n=` kwarg and you can get the columns using `expand`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "85a5cd2c",
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"names\"].str.split(\" \", n=2, expand=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e6bb64b2",
   "metadata": {},
   "source": [
    "```{admonition} Exercise\n",
    "Using vectorised operations, create a new column with the index position where the first vowel occurs for each row in the `names` column.\n",
    "```\n",
    "\n",
    "Here's an example of using a regex function with **pandas**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2e8781ba",
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"names\"].str.extract(\"(\\w+)\", expand=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7fb6d243",
   "metadata": {},
   "source": [
    "There are a few more vectorised string operations that are useful.\n",
    "\n",
    "| Method | Description |\n",
    "|-|-|\n",
    "| `get()` | Index each element |\n",
    "| `slice()` | Slice each element |\n",
    "| `slice_replace()` | Replace slice in each element with passed value |\n",
    "| `cat()` | Concatenate strings |\n",
    "| `repeat()` | Repeat values |\n",
    "| `normalize()` | Return Unicode form of string |\n",
    "| `pad()` | Add whitespace to left, right, or both sides of strings |\n",
    "| `wrap()` | Split long strings into lines with length less than a given width |\n",
    "| `join()` | Join strings in each element of the Series with passed separator |\n",
    "| `get_dummies()` | extract dummy variables as a data frame |\n",
    "\n",
    "\n",
    "The `get()` and `slice()` methods give access to elements of the lists returned by `split()`. Here's an example that combines `split()` and `get()`:\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ba13d894",
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"names\"].str.split().str.get(-1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4ce15edb",
   "metadata": {},
   "source": [
    "If we have a column with tags split by a symbol, we can use the `get_dummies()` function to split it out. For example, let's create a data frame with a single column that mixes subject and nationality tags:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "056147d6",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(\n",
    "    {\n",
    "        \"names\": [\n",
    "            \"ada lovelace\",\n",
    "            \"adam smith\",\n",
    "            \"elinor ostrom\",\n",
    "            \"grace hopper\",\n",
    "            \"jean bartik\",\n",
    "        ],\n",
    "        \"tags\": [\"uk; cs\", \"uk; econ\", \"usa; econ\", \"usa; cs\", \"usa; cs\"],\n",
    "    }\n",
    ")\n",
    "df"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f44b0d84",
   "metadata": {},
   "source": [
    "If we now use `str.get_dummies()` and split on `;` we can get a data frame of dummies."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a5cbc10f",
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"tags\"].str.get_dummies(\";\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58521740",
   "metadata": {},
   "source": [
    "## Reading Text In\n",
    "\n",
    "### Text file\n",
    "\n",
    "If you have just a plain text file, you can read it in like so:\n",
    "\n",
    "```python\n",
    "fname = 'book.txt'\n",
    "with open(fname, encoding='utf-8') as f:\n",
    "    text_of_book = f.read()\n",
    "```\n",
    "\n",
    "You can also read a text file directly into a **pandas** data frame using \n",
    "\n",
    "```python\n",
    "df = pd.read_csv('book.txt', delimiter = \"\\n\")\n",
    "```\n",
    "\n",
    "In the above, the delimiter for different rows of the data frame is set as \"\\n\", which means new line, but you could use whatever delimiter you prefer.\n",
    "\n",
    "```{admonition} Exercise\n",
    "Download the file 'smith_won.txt' using this [link](https://github.com/aeturrell/coding-for-economists/blob/main/data/smith_won.txt) (use right-click and save as). Then read the text in using **pandas**.\n",
    "```\n",
    "\n",
    "### CSV file\n",
    "\n",
    "CSV files are already split into rows. By far the easiest way to read in csv files is using **pandas**,\n",
    "\n",
    "```python\n",
    "df = pd.read_csv('book.csv')\n",
    "```\n",
    "\n",
    "Remember that **pandas** can read many other file types too."
   ]
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "9d7534ecd9fbc7d385378f8400cf4d6cb9c6175408a574f1c99c5269f08771cc"
  },
  "jupytext": {
   "cell_metadata_filter": "-all",
   "encoding": "# -*- coding: utf-8 -*-",
   "formats": "md:myst",
   "main_language": "python"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  },
  "toc-showtags": true
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
